#include <stdio.h>
#include <stdlib.h>
#include <omp.h>
#include <mpi.h>

#include "io.h"
#include "ParallelJacobisMethod.h"

#define TAG 0
#define STOP 0
#define MASTER 0

#define TIMER
#define VERBOSE

//#define DEBUG_NPROC
//#define DEBUG_MTR
//#define DEBUG_SLV
//#define DEBUG_OUTPUT


void copyXB(struct unknownsStrip *X, struct extendedMatrix *M);

int main(int argc, char* argv[]){

    // MPI Control 
    int my_rank;
    int nProcess;
    MPI_Status status;
    double *buffer;

    // Numeric Method
    unsigned long interactions=0;
    double E;

    struct unknownsStrip *X;
    struct unknownsStrip *partialX;
    struct extendedMatrix *M;

    #ifdef TIMER
        struct timeval t_zero, t_final;
        double delta_t;
        gettimeofday(&t_zero, NULL);
    #endif

    if(argc!=2){
        printf("Entrada Incorreta");
        exit(-1);
    }

//    #pragma omp parallel
//    #pragma omp sections
    {
//        #pragma omp section
        {
            M = readMatrix(argv[1]);
            X = initStrip();
            partialX = initStrip();    
            // Init X with B( X=b )
            copyXB(X, M);
        }
//        #pragma omp section
        {
            MPI_Init(&argc, &argv);
            MPI_Comm_size(MPI_COMM_WORLD, &nProcess);
            MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
            #ifdef DEBUG_NPROC
                printf("Total %d from Process[%d]\n"
                , nProcess, my_rank);
            #endif
        }
    }

//    #pragma omp barrier

master:
 
    if(my_rank==MASTER){
    
        partialX->vector=(double*)calloc(M->J_ORDER, sizeof(double));
        partialX->isItok = OK;        

        #ifdef DEBUG_MTR
            printf("Listen and Obey!\n");
        #endif

        #ifdef DEBUG_OUTPUT
            { auto int i;
            printf("X[%ld]", interactions);
            for(i=0; i<X->length; i++){
                printf("%.2lf ", X->vector[i]);
            } 
            printf("\n"); }
        #endif

        while(!STOP){
            auto int SLAVE, i, U, L;

            interactions++;

            // Sometimes this works... So no room for doubt
//#pragma omp parallel for private(partialX, L, U, i) lastprivate(SLAVE)

            for(SLAVE=1; SLAVE<(nProcess); SLAVE++){
                
                findMyChunk(M, partialX, SLAVE, nProcess);

                #ifdef DEBUG_MTR
                    printf("MTR - Vou pegar do %u ao %u ...",
                    partialX->lowerChunk, partialX->upperChunk);
                    printf("MTR - To esperando...");
                    fflush(stdout);
                #endif

                MPI_Recv(partialX->vector, X->length, MPI_DOUBLE,
                SLAVE, TAG, MPI_COMM_WORLD, &status);

                #ifdef DEBUG_MTR
                    printf("MTR - Chegou[%d]\n", SLAVE);
                    fflush(stdout);
                #endif

                L = partialX->lowerChunk;
                U = partialX->upperChunk;

                for(i=0; i<(U-L); i++)
                    X->vector[L+i] = partialX->vector[i];
            }

            #ifdef DEBUG_OUTPUT
                printf("X[%ld]", interactions);
                for(i=0; i<X->length; i++){
                    printf("%.2lf ", X->vector[i]);
                } 
                printf("\n");
            #endif

//            #pragma omp barrier
            MPI_Bcast(X->vector, X->length, MPI_DOUBLE,
            MASTER, MPI_COMM_WORLD);            

            E = numericValidation(M, X->vector);
            if(E>0){
                #ifdef VERBOSE
                    printf("\nIterations: %lu\n", interactions);
                    printf("RowTest: %lu => [%lf] =? %lf\n",
                    M->J_ROW_TEST, M->vector[M->J_ROW_TEST]-E,
                    M->vector[M->J_ROW_TEST]);
                #endif
                break;
            }
            
            if(interactions > M->J_ITE_MAX){
                #ifdef VERBOSE
                    printf("\nIterations: %lu - MAX INTERACTIONS HALT\n"
                    , interactions);
                    printf("RowTest: %lu => [%lf] =? %lf\n",
                    M->J_ROW_TEST, M->vector[M->J_ROW_TEST]-E,
                    M->vector[M->J_ROW_TEST]);
                #endif
            break;
            }
        }
        freeStrip(partialX);
 
    }else{
slave:
        #ifdef DEBUG_SLV
            printf("Live to Serve![%d]\n", my_rank);
        #endif

        #ifdef DEBUG_NPROC
            {   auto int i;
            printf("X[%u]=", X->length);
            for(i=0; i<X->length; i++){
                printf("%.1lf", X->vector[i]);
            }}
        #endif

        findMyChunk(M, X, my_rank, nProcess);

        while(! STOP){
            interactions++;
            partialX=(struct unknownsStrip*) ParallelJacobisMethod(M,X);

            #ifdef DEBUG_SLV
                printf("SLV[%d] vou mandar...", my_rank);
                fflush(stdout);
            #endif

            MPI_Send(partialX->vector, partialX->length, MPI_DOUBLE,
                    MASTER, TAG, MPI_COMM_WORLD);

            #ifdef DEBUG_SLV
                printf("... mandei. Travei[%d]!\n", my_rank);
                fflush(stdout);
            #endif
            freeStrip(partialX);

//            #pragma omp barrier
            
            MPI_Bcast(X->vector, X->length, MPI_DOUBLE,
            MASTER, MPI_COMM_WORLD);            
            
            #ifdef DEBUG_SLV
                printf(" Destravei[%d], X->%lf!\n", my_rank,
                X->vector[64]);
                fflush(stdout);
            #endif
            
            // If this condicions were matched, it's time to stop.
            E = numericValidation(M, X->vector);
            if(E>0) break;
            if(interactions > M->J_ITE_MAX) break;
        }
   }

    freeStrip(X);
    freeMatrix(M);

    #ifdef TIMER
        gettimeofday(&t_final, NULL);
        delta_t = t_final.tv_sec - t_zero.tv_sec +
        ( t_final.tv_usec - t_zero.tv_usec ) / 1e6;
        printf("Tempo = %.3lf segundos\n", delta_t);    
    #endif


//    MPI_Finalize(); // This will freezy the cluster
    MPI_Abort(MPI_COMM_WORLD, 666); // but that will do!

// than 2>/dev/null , Don't worry, be happy! 

    return 0;
}

void copyXB(struct unknownsStrip *X, struct extendedMatrix *M){
    int i, N;

    N = M->J_ORDER;
    X->length = N;

    if(!X->isItok) X->vector = (double*) calloc(N, sizeof(double));
    else{
        if(X->length != N) free(X->vector); 
        X->vector = (double*) calloc(N, sizeof(double));
    }

    for(i=0; i<N; i++){
        X->vector[i] = M->vector[i];
        #ifdef DEBUG_MTR
            printf("%lf", X->vector[i]);
        #endif
    }
}
